class_name DragAndDrop
extends Node
## 版本：1.0.0
## 功能：Node2D 节点拖拽。
## 使用：将该脚本挂载到 Node 节点上，作为待拖拽对象的子节点，配置 drag_area 和 target 导出变量。

#region Signals
## 拖拽开始
signal drag_started

## 拖拽取消
signal drag_canceled(start_position: Vector2)

## 拖拽释放
signal dropped(start_position: Vector2)
#endregion

#region Exported parameters
## 是否启用拖拽功能
@export var enabled: bool = true

## 检测拖拽的区域
@export var drag_area: Area2D

## 应用拖拽的对象
@export var target: Node2D
#endregion

#region Internal parameters
## 被拖拽对象的起始位置，即拖拽开始时的位置
var start_position: Vector2

## 被拖拽对象的全局位置和鼠标位置的偏移量
var mouse_offset := Vector2.ZERO

## 判断当前对象是否正在被拖拽中
var dragging := false
#endregion


func _ready() -> void:
	# 检查是否设置了应用拖拽的对象和检测区域
	assert(target and drag_area, "No target and detector set for DragAndDrop Component!")
	
	# 接收 drag_area 的事件
	# INFO: 因为判断目标拖拽不需要返回 Area2D 的形状，因此解绑 input_event 的 shape_idx: int 参数
	drag_area.input_event.connect(_on_target_input_event.unbind(1))


func _process(_delta: float) -> void:
	if dragging and target: # 如果正在拖拽对象
		# 每帧都随鼠标移动被拖拽对象
		target.global_position = target.get_global_mouse_position() + mouse_offset


func _input(event: InputEvent) -> void:
	if dragging and event.is_action_pressed("cancel_drag"): # 如果拖拽时取消拖拽
		_on_drag_canceled()
	elif dragging and event.is_action_released("start_drag"): # 如果拖拽时放置
		_on_dropped()


func _on_drag_started() -> void:
	dragging = true
	start_position = target.global_position
	target.add_to_group("dragging")
	target.z_index = 99
	mouse_offset = target.global_position - target.get_global_mouse_position()
	drag_started.emit()


func _on_drag_end() -> void:
	dragging = false
	target.remove_from_group("dragging")
	target.z_index = 0


func _on_drag_canceled() -> void:
	_on_drag_end()
	drag_canceled.emit(start_position)


func _on_dropped() -> void:
	_on_drag_end()
	dropped.emit(start_position)


## 处理待拖拽对象的事件
func _on_target_input_event(_viewport: Node, event: InputEvent) -> void:
	if not enabled:
		return

	# 使用 dragging 组来实现只能一次拖拽一个对象
	var drag_object := get_tree().get_first_node_in_group("dragging")
	
	if not dragging and drag_object: # 如果当前对象没有被拖拽且已经存在一个被拖拽对象
		# 禁止拖拽多个对象，所以直接返回
		return
	
	if not dragging and event.is_action_pressed("start_drag"): # 如果当前对象没有被拖拽且按下了拖拽按键
		_on_drag_started()
